/*
 * socketObject.cpp
 *
 *  Created on: 14.03.2009
 *      Author: Christian Stupp
 */

#include "../include/socketObject.h"

int oal::socketObject::sockets = 0;

void oal::socketObject::Start() {
#ifdef _WIN32
	if (!sockets) {
		WSADATA info;
		if (WSAStartup(MAKEWORD(2,0), &info)) {
		  throw "Could not start WSA";
		}
	}
#endif
	++sockets;
}


void oal::socketObject::startCompress(void) {
	this->compress = new compressObject("zlib");
	this->compressStart = true;
}

void oal::socketObject::destroyCompress(void) {
	delete this->compress;
	this->compressStart = false;
}

void oal::socketObject::startEncrypt(std::string method, std::string password) {
	this->encrypt = new cryptObject(method,password);
	this->encryptStart = true;
}
void oal::socketObject::destroyEncrypt(void) {
	delete this->encrypt;
	this->encryptStart = false;
}

void oal::socketObject::End() {
#ifdef _WIN32
	WSACleanup();
#endif
}

oal::socketObject::socketObject(): s_(0) {
	Start();
	this->compress = 0;
	this->encrypt = 0;
	this->encryptStart = false;
	this->compressStart = false;
	// UDP: use SOCK_DGRAM instead of SOCK_STREAM
	s_ = socket(AF_INET,SOCK_STREAM,0);

	if (s_ == INVALID_SOCKET) {
	throw "INVALID_SOCKET";
	}

#ifndef _WIN32
	int yes = 1;
	if( setsockopt(s_, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
		throw ("Can't create socket options SO_REUSEADDR");
	}
#endif

	putenv((char*)("SERVER_IP=" + this->getIp()).c_str());
	refCounter_ = new int(1);
}

oal::socketObject::socketObject(SOCKET s) : s_(s) {
	Start();
	this->compress = 0;
	this->encrypt = 0;
	this->encryptStart = false;
	this->compressStart = false;
	putenv((char*)("SERVER_IP=" + this->getIp()).c_str());
	refCounter_ = new int(1);
};

oal::socketObject::~socketObject() {
	if (! --(*refCounter_)) {
		Close();
		delete refCounter_;
	}

	if(this->encryptStart == true) {
		delete this->encrypt;
	}
	if(this->compressStart == true) {
		delete this->compress;
	}
	--sockets;
	if (!sockets) End();
}

oal::socketObject::socketObject(const socketObject& o) {

	this->compress = 0;
	this->encrypt = 0;
	this->encryptStart = false;
	this->compressStart = false;
	refCounter_=o.refCounter_;
	(*refCounter_)++;
	s_ = o.s_;

  sockets++;
}

oal::socketObject& oal::socketObject::operator=(oal::socketObject& o) {
  (*o.refCounter_)++;

  refCounter_=o.refCounter_;
  s_         =o.s_;

 sockets++;

  return *this;
}

void oal::socketObject::Close() {
#ifdef _WIN32
	closesocket(s_);
#else
	close(s_);
#endif
}

std::string oal::socketObject::ReceiveAll() {
std::string ret;
char buf[1024];
	while(1) {
		int rv = recv (s_, buf, 1024, 0);
		if(rv <= 0) {
			break;
		}
		debug->add("Receiving " + intToStr(rv) + " Bytes",__FILE__,__LINE__,9);

		std::string t;

		t.assign (buf, rv);
		ret += t;
	}
	return ret;
}


std::string oal::socketObject::ReceiveClearBytes() {
std::string ret;
char buf[1024];

	while(1) {
		u_long arg = 0;
		if (ioctlsocket(s_, FIONREAD, &arg) != 0) {
			break;
		}

		if (arg == 0) {
			break;
		}

		if (arg > 1024 || arg < 1) arg = 1024;

		int rv = recv (s_, buf, arg, 0);
		debug->add("Receiving " + intToStr(rv) + " Bytes",__FILE__,__LINE__,9);

		std::string t;

		t.assign (buf, rv);
		ret += t;
	}
	return ret;
}
std::string oal::socketObject::ReceiveBytes() {
  std::string ret;
  // Receiving the first line to get the content length
  string line = staticObject::trim(this->ReceiveLine());

  int toReceive = atoi(line.c_str());
  int allReceive = 0;
  debug->add("Receiving from Client" + line,__FILE__,__LINE__,9);
  while (toReceive > allReceive) {
	  ret = this->ReceiveClearBytes();
	  allReceive += ret.length();
  }

  debug->add("Before encode " + intToStr(ret.length()) + " Bytes",__FILE__,__LINE__,9);
  if(this->encryptStart == true) {
	  ret = this->encrypt->performCryption(ret);
  }
  debug->add("Before compress " + intToStr(ret.length()) + " Bytes",__FILE__,__LINE__,9);
  if(this->compressStart == true) {
	  ret = this->compress->performDeCompress(ret);
  }
  debug->add("Result Length" + intToStr(ret.length()) + " Bytes",__FILE__,__LINE__,9);
  debug->add("Result " + ret + " Bytes",__FILE__,__LINE__,9);
  return ret;
}


std::string oal::socketObject::ReceiveLine() {
  std::string ret;

//  u_long arg = 0;
  while (1) {
    char r;

    switch(recv(s_, &r, 1, 0)) {
      case 0: // not connected anymore;
              // ... but last line sent
              // might not end in \n,
              // so return ret anyway.
        return ret;
      case -1:
        return "";
//      if (errno == EAGAIN) {
//        return ret;
//      } else {
//      // not connected anymore
//      return "";
//      }
    }
    ret += r;
    if (r == '\n')  break;
  }

  return ret;
}

void oal::socketObject::SendLine(std::string s) {
	s += "\n";
	int sending = send(s_,s.c_str(),s.length(),0);
    debug->add("Sending " + intToStr(sending) + " from " + intToStr(s.length()) + " Bytes",__FILE__,__LINE__,9);
}

void oal::socketObject::SendBytes(const std::string& s) {
	string data = s;
	if(this->compressStart == true) {
		data = this->compress->performCompress(data);
	}
	if(this->encryptStart == true) {
		data = this->encrypt->performCryption(data);
	}
	string contentLength = (string)intToStr(data.length()) + "\n";
	// First we send the content length
	send(s_,contentLength.c_str(),contentLength.length(),0);
	int sending = this->SendClearBytes(data);
	debug->add("Sending " + intToStr(sending) + " from " + intToStr(data.length()) + " Bytes",__FILE__,__LINE__,9);

}

int oal::socketObject::SendClearBytes(const std::string& s) {
	string data = s;
	return send(s_,data.c_str(),data.length(),0);
}

std::string oal::socketObject::getIp(void) {
	std::string result="";
	PHOSTENT hostinfo;
	char hostname[255];
	if( gethostname ( hostname, sizeof(hostname)) == 0) {
		if((hostinfo = gethostbyname(hostname)) != NULL) {
			result = inet_ntoa (*(struct in_addr *)*hostinfo->h_addr_list);

		}
	}
	return result;
}
